const postcss = require('postcss');
const BigNum = require('bignumber.js');
const {createPropListMatcher} = require('./utils/prop-list-matcher');
const pxRegex = /"[^"]+"|'[^']+'|url\([^)]+\)|(\d*\.?\d+)px/g;

/**
 * 将px替换为rem逻辑
 * @param m
 * @param $1
 * @return {string|*}
 */
const pxReplaceFn = (m, $1) => {
  if (!$1) return m;
  let pixels = parseFloat($1);
  let parsedVal = new BigNum(pixels).div(100).toNumber();
  return parsedVal === 0 ? '0' : `${parsedVal}rem`;
};

/**
 * 是否在排除列表中
 * @param reg
 * @param file
 * @return {boolean}
 */
const isExclude = (reg, file) => {
  if (!isType(reg, '[object RegExp]')) {
    throw new Error('options.exclude should be RegExp.');
  }
  return file.match(reg) !== null;
};
const declarationExists = (decls, prop, value) => {
  return decls.some(function (decl) {
    return (decl.prop === prop && decl.value === value);
  });
};
/**
 * 类型判断
 * @param variable
 * @param type
 * @return {boolean}
 */
const isType = (variable, type) => {
  return Object.prototype.toString.call(variable) === type;
};

const blacklistedSelector = (blacklist, selector) => {
  if (typeof selector !== 'string') return;
  return blacklist.some(function (regex) {
    if (typeof regex === 'string') return selector.indexOf(regex) !== -1;
    return selector.match(regex);
  });
};

let defaults = {
  propList: ['*'],
  selectorBlackList: [],
  replace: true,
  exclude: [],
};

module.exports = postcss.plugin('postcss-px-to-remjs', options => {
  options = Object.assign({}, defaults, options);
  let satisfyPropList = createPropListMatcher(options.propList);
  return css => {
    css.walkRules(rule => {
      let file = rule.source && rule.source.input.file;
      if (options.exclude && file) {
        if (isType(options.exclude, '[object RegExp]')) {
          if (isExclude(options.exclude, file)) return;
        } else if (isType(options.exclude, '[object Array]')) {
          for (let i = 0; i < options.exclude.length; i++) {
            if (isExclude(options.exclude[i], file)) return;
          }
        } else {
          throw new Error('options.exclude should be RegExp or Array.');
        }
      }
      if (blacklistedSelector(options.selectorBlackList, rule.selector)) return;

      rule.walkDecls(function (decl, i) {
        if (decl.value.indexOf('px') === -1) return;
        if (!satisfyPropList(decl.prop)) return;
        let value = decl.value.replace(pxRegex, pxReplaceFn);
        if (declarationExists(decl.parent, decl.prop, value)) return;
        if (options.replace) {
          decl.value = value;
        } else {
          decl.parent.insertAfter(i, decl.clone({value: value}));
        }
      });
    });
  };
});
